﻿using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.InteropServices;
using EnvDTE;
using Microsoft.VisualStudio;
using Microsoft.VisualStudio.Shell;
using Microsoft.VisualStudio.Shell.Interop;
using Microsoft.VisualStudio.TextManager.Interop;

namespace IncrementalSave
{
    public class FileHistoryTracker : IVsRunningDocTableEvents, IVsSolutionEvents
    {
        private uint _rdtCookie;
        private List<HistoryItem> _historyItems = new List<HistoryItem>();
        private IVsRunningDocumentTable _rdt;
        private uint _solutionCookie;
        private IncrementalSavePackage _package;

        #region Event HistoryItemsChanged
        
        internal event EventHandler HistoryItemsChanged;

        private void OnHistoryItemsChanged()
        {
            EventHandler handler = HistoryItemsChanged;
            if (handler != null) handler(this, EventArgs.Empty);
        }

        //private void OnHistoryItemsChanged(uint docCookie)
        //{
        //    HistoryItemsChangedEventArgs args = new HistoryItemsChangedEventArgs(GetHistoryItem(docCookie));
        //    EventHandler<HistoryItemsChangedEventArgs> handler = HistoryItemsChanged;
        //    if (handler != null) handler(this, args);
        //}

        //internal class HistoryItemsChangedEventArgs : EventArgs
        //{
        //    internal HistoryItemsChangedEventArgs(HistoryItem historyItem)
        //    {
        //        HistoryItem = historyItem;
        //    }
        //    public HistoryItem HistoryItem { get; set; }
        //}
        #endregion

        public List<HistoryItem> HistoryItems{get { return _historyItems; }}

        public FileHistorySaveProvider SaveProvider { get; set; }

        internal FileHistoryTracker(IncrementalSavePackage incrementalSavePackage)
        {
            _package = incrementalSavePackage;
            SaveProvider = incrementalSavePackage.SaveProvider;
            var history = SaveProvider.LoadHistory();
            _historyItems = history.ToList();
            OnHistoryItemsChanged();
            _rdt = (IVsRunningDocumentTable)Package.GetGlobalService(typeof(SVsRunningDocumentTable));
            _rdt.AdviseRunningDocTableEvents(this, out _rdtCookie);
            var vsSolution = (IVsSolution)Package.GetGlobalService(typeof(SVsSolution));
            vsSolution.AdviseSolutionEvents(this, out _solutionCookie);
        }
        
        internal void Dispose()
        {
            _rdt = (IVsRunningDocumentTable)Package.GetGlobalService(typeof(SVsRunningDocumentTable));
            _rdt.UnadviseRunningDocTableEvents(_rdtCookie);

            var vsSolution = (IVsSolution)Package.GetGlobalService(typeof(SVsSolution));
            vsSolution.UnadviseSolutionEvents(_solutionCookie);
        }
        
        public int OnAfterSave(uint docCookie)
        {
            HistoryItem historyItem = GetHistoryItem(docCookie);
            SaveProvider.IncrementalSave(historyItem);
            HistoryItems.Add(historyItem);
            OnHistoryItemsChanged();
            return VSConstants.S_OK;
        }

        private HistoryItem GetHistoryItem(uint docCookie)
        {
            IVsHierarchy trash2 = null;
            IntPtr ppunkDocData = IntPtr.Zero;
            uint trash = 0;
            string documentFilePath = null;
            _rdt.GetDocumentInfo(docCookie, out trash, out trash, out trash, out documentFilePath, out trash2, out trash, out ppunkDocData);
            string text = File.ReadAllText(documentFilePath);
            var item = new HistoryItem() { DocCookie = docCookie, DocumentFilePath = documentFilePath, SaveTime = DateTime.Now, FileText = text};
            return item;
        }

        #region IVsRunningDocTableEvents Methods
        public int OnAfterFirstDocumentLock(uint docCookie, uint dwRDTLockType, uint dwReadLocksRemaining, uint dwEditLocksRemaining)
        {
            return VSConstants.S_OK;
        }

        public int OnBeforeLastDocumentUnlock(uint docCookie, uint dwRDTLockType, uint dwReadLocksRemaining, uint dwEditLocksRemaining)
        {
            return VSConstants.S_OK;
        }

        public int OnAfterAttributeChange(uint docCookie, uint grfAttribs)
        {
            return VSConstants.S_OK;
        }

        public int OnBeforeDocumentWindowShow(uint docCookie, int fFirstShow, IVsWindowFrame pFrame)
        {
            return VSConstants.S_OK;
        }

        public int OnAfterDocumentWindowHide(uint docCookie, IVsWindowFrame pFrame)
        {
            return VSConstants.S_OK;
        }
        #endregion

        #region Implementation of IVsSolutionEvents

        public int OnAfterOpenProject(IVsHierarchy pHierarchy, int fAdded)
        {
            return VSConstants.S_OK;
        }

        public int OnQueryCloseProject(IVsHierarchy pHierarchy, int fRemoving, ref int pfCancel)
        {
            return VSConstants.S_OK;
        }

        public int OnBeforeCloseProject(IVsHierarchy pHierarchy, int fRemoved)
        {
            return VSConstants.S_OK;
        }

        public int OnAfterLoadProject(IVsHierarchy pStubHierarchy, IVsHierarchy pRealHierarchy)
        {
            Debug.WriteLine("OnAfterLoadProject called");
            _historyItems = SaveProvider.LoadHistory().ToList();
            OnHistoryItemsChanged();
            return VSConstants.S_OK;
        }

        public int OnQueryUnloadProject(IVsHierarchy pRealHierarchy, ref int pfCancel)
        {
            return VSConstants.S_OK;
        }

        public int OnBeforeUnloadProject(IVsHierarchy pRealHierarchy, IVsHierarchy pStubHierarchy)
        {
            return VSConstants.S_OK;
        }

        public int OnAfterOpenSolution(object pUnkReserved, int fNewSolution)
        {
            Debug.WriteLine("OnAfterOpenSolution called");
            _historyItems = SaveProvider.LoadHistory().ToList();
            OnHistoryItemsChanged();
            return VSConstants.S_OK;
        }

        public int OnQueryCloseSolution(object pUnkReserved, ref int pfCancel)
        {
            return VSConstants.S_OK;
        }

        public int OnBeforeCloseSolution(object pUnkReserved)
        {
            return VSConstants.S_OK;
        }

        public int OnAfterCloseSolution(object pUnkReserved)
        {
            HistoryItems.Clear();
            OnHistoryItemsChanged();
            return VSConstants.S_OK;
        }

        #endregion

        public void OpenView(HistoryItem historyItem)
        {
            if (historyItem == null)
                return;
            ProjectItem foundProjectItem = _package.Dte.Solution.FindProjectItem(historyItem.DocumentFilePath);
            var window = foundProjectItem.Open();
            //window.Activate();
            Microsoft.VisualStudio.OLE.Interop.IServiceProvider sp = (Microsoft.VisualStudio.OLE.Interop.IServiceProvider)_package.Dte;
            ServiceProvider serviceProvider = new ServiceProvider(sp);
            IVsUIHierarchy uiHierarchy;
            uint itemID;
            IVsWindowFrame windowFrame;
            IVsTextView vsTextView = null;
            if (VsShellUtilities.IsDocumentOpen(serviceProvider, historyItem.DocumentFilePath, Guid.Empty,
                                            out uiHierarchy, out itemID, out windowFrame))
            {
                // Get the IVsTextView from the windowFrame.
                vsTextView =  VsShellUtilities.GetTextView(windowFrame);

                int endCol, endLine, anchorCol, anchorLine;
                vsTextView.GetSelection(out anchorLine, out anchorCol, out endLine, out endCol);
                TextSelMode selectionMode = vsTextView.GetSelectionMode();

                IVsTextLines buffer;
                vsTextView.GetBuffer(out buffer);
                buffer.LockBuffer();
                object editPointObj, endPointObj;
                buffer.CreateEditPoint(0, 0, out editPointObj);
                EditPoint editPoint = (EditPoint) editPointObj;
                int lastLine, lastCharacter;
                buffer.GetLastLineIndex(out lastLine, out lastCharacter);
                buffer.CreateEditPoint(lastLine, lastCharacter, out endPointObj);
                EditPoint endPoint = (EditPoint) endPointObj;
                string currentText = editPoint.GetText(endPoint);

                editPoint.Delete(endPoint);
                editPoint.Insert(historyItem.FileText);
                buffer.UnlockBuffer();

                int[][] editMatrix = ComparisonHelper.EditDistance(currentText, historyItem.FileText);

                vsTextView.SetSelectionMode(selectionMode);
                vsTextView.SetSelection(anchorLine, anchorCol, endLine, endCol);
            }

        }
    }
}
